﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

namespace Foundation
{
    /// <summary>
    /// View Model Base.
    /// </summary>
    [DataContract]
    public class ViewModelBase : PropertyNameProvider, INotifyPropertyChanging, INotifyPropertyChanged, IDataErrorInfo
    {
        public const string IndexerName = System.Windows.Data.Binding.IndexerName; // "Item[]"
        protected Dictionary<string, object> Values = new Dictionary<string, object>();
        public event PropertyChangingEventHandler PropertyChanging = (sender, args) => { };
        public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };

        /// <summary>
        /// Gets or sets the <see cref="System.Object"/> with the specified key.
        /// </summary>
        public object this[string key]
        {
            get { return Values.ContainsKey(key) ? Values[key] : null; }
            set
            {
                RaisePropertyChangingEvent(IndexerName);
                if (Values.ContainsKey(key))
                    Values[key] = value;
                else Values.Add(key, value);
                RaisePropertyChangedEvent(IndexerName);
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="System.Object"/> with the specified key.
        /// </summary>
        public object this[string key, object defaultValue]
        {
            get
            {
                if (!Values.ContainsKey(key))
                    Values.Add(key, defaultValue);
                return Values[key];
            }
            set { this[key] = value; }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        string IDataErrorInfo.this[string columnName]
        {
            get { return GetDataErrorInfo(columnName); }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public string Error
        {
            get { return GetDataErrorInfo(); }
        }

        /// <summary>
        /// Gets the data error info.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>Data error info</returns>
        protected virtual string GetDataErrorInfo(string propertyName = null)
        {
            return null;
        }

        [DataMember]
        private Dictionary<string, object> ImplicitProperties
        {
            get
            {
                var explicitProperties =
                    GetType()
                        .GetProperties(BindingFlags.Instance | BindingFlags.Static |
                                       BindingFlags.Public | BindingFlags.NonPublic)
                        .Select(p => p.Name)
                        .ToList();
                return Values.Where(p => !explicitProperties.Contains(p.Key)).ToDictionary(
                    pair => pair.Key,
                    pair => pair.Value is ValueType ? pair.Value.ToString() : pair.Value);
            }
            set { if (value != null) value.ToList().ForEach(pair => this[pair.Key] = pair.Value); }
        }

        public void RaisePropertyChangingEvent(string propertyName)
        {
            PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
        }

        public void RaisePropertyChangedEvent(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        [OnDeserializing]
        private void Initialize(StreamingContext context = default(StreamingContext))
        {
            if (PropertyChanging == null) PropertyChanging = (sender, args) => { };
            if (PropertyChanged == null) PropertyChanged = (sender, args) => { };
            if (Values == null) Values = new Dictionary<string, object>();
        }
    }
}